# SEE modeldata package for new datasets
library(tidyverse) # for graphing and data cleaning
library(tidymodels) # for modeling
library(stacks) # for stacking models
library(naniar) # for examining missing values (NAs)
library(lubridate) # for date manipulation
library(moderndive) # for King County housing data
library(DALEX) # for model interpretation
library(DALEXtra) # for extension of DALEX
library(patchwork) # for combining plots nicely
library(dbplyr) # for SQL query "cheating" - part of tidyverse but needs to be loaded separately
library(mdsr) # for accessing some databases - goes with Modern Data Science with R textbook
library(RMySQL) # for accessing MySQL databases
library(RSQLite) # for accessing SQLite databases
#mapping
library(maps) # for built-in maps
library(sf) # for making maps using geom_sf
library(ggthemes) # Lisa added - I like theme_map() for maps :)
#tidytext
library(tidytext) # for text analysis, the tidy way!
library(textdata)
library(reshape2)
library(wordcloud) # for wordcloud
library(stopwords)
library(plotly)
theme_set(theme_minimal()) # Lisa's favorite theme
When you finish the assignment, remove the # from the options chunk at the top, so that messages and warnings aren’t printed. If you are getting errors in your code, add error = TRUE so that the file knits. I would recommend not removing the # until you are completely finished.
Put it on GitHub!
From now on, GitHub should be part of your routine when doing assignments. I recommend making it part of your process anytime you are working in R, but I’ll make you show it’s part of your process for assignments.
Task: When you are finished with the assignment, post a link below to the GitHub repo for the assignment. If you want to post it to your personal website, that’s ok (not required). Make sure the link goes to a spot in the repo where I can easily find this assignment. For example, if you have a website with a blog and post the assignment as a blog post, link to the post’s folder in the repo. As an example, I’ve linked to my GitHub stacking material here.
Github Repo
Local Interpretable Machine Learning
You are going to use the King County house data and the same random forest model to predict log_price that I used in the tutorial.
Tasks:
data("house_prices")
# Create log_price and drop price variable
house_prices <- house_prices %>%
mutate(log_price = log(price, base = 10)) %>%
# make all integers numeric ... fixes prediction problem
mutate(across(where(is.integer), as.numeric)) %>%
select(-price)
house_split <- initial_split(house_prices,
prop = .75)
house_training <- training(house_split)
house_testing <- testing(house_split)
ranger_recipe <-
recipe(formula = log_price ~ .,
data = house_training) %>%
step_date(date,
features = "month") %>%
# Make these evaluative variables, not included in modeling
update_role(all_of(c("id",
"date")),
new_role = "evaluative")
#define model
ranger_spec <-
rand_forest(mtry = 6,
min_n = 10,
trees = 200) %>%
set_mode("regression") %>%
set_engine("ranger")
#create workflow
ranger_workflow <-
workflow() %>%
add_recipe(ranger_recipe) %>%
add_model(ranger_spec)
#fit the model
set.seed(712) # for reproducibility - random sampling in random forest choosing number of variables
ranger_fit <- ranger_workflow %>%
fit(house_training)
rf_explain <-
explain_tidymodels(
model = ranger_fit,
data = house_training %>% select(-log_price),
y = house_training %>% pull(log_price),
label = "rf"
)
## Preparation of a new explainer is initiated
## -> model label : rf
## -> data : 16210 rows 20 cols
## -> data : tibble converted into a data.frame
## -> target variable : 16210 values
## -> predict function : yhat.workflow will be used ( [33m default [39m )
## -> predicted values : No value for predict function target column. ( [33m default [39m )
## -> model_info : package tidymodels , ver. 0.1.2 , task regression ( [33m default [39m )
## -> predicted values : numerical, min = 5.029925 , mean = 5.665048 , max = 6.741405
## -> residual function : difference between y and yhat ( [33m default [39m )
## -> residuals : numerical, min = -0.3493318 , mean = 0.0004542325 , max = 0.3209772
## [32m A new explainer has been created! [39m
new_obs1 <- house_testing %>% slice(306)
new_obs2 <- house_testing %>% slice(5366)
new_obs3 <- house_testing %>% slice(10)
- Choose 3 new observations and do the following for each observation:
- Construct a break-down plot using the default ordering. Interpret the resulting graph. Which variables contribute most to each observation’s prediction?
pp_rf1 <- predict_parts(explainer = rf_explain,
new_observation = new_obs1,
type = "break_down")
pp_rf2 <- predict_parts(explainer = rf_explain,
new_observation = new_obs2,
type = "break_down")
pp_rf3 <- predict_parts(explainer = rf_explain,
new_observation = new_obs3,
type = "break_down")
plot(pp_rf1)

The year the house was built, the size of the living space, and the location seem to be the most important for this observation. They are all positive for the prediction of price.
plot(pp_rf2)

For this observation it seems to be similar variables that are the most important, but only the location is positive.
plot(pp_rf3)

For this observation the same variables are the most important and they are all strongly positive.
- Construct a SHAP graph and interpret it. Does it tell a similar story to the break-down plot?
rf_shap1 <-predict_parts(explainer = rf_explain,
new_observation = new_obs1,
type = "shap",
B = 10
)
rf_shap2 <-predict_parts(explainer = rf_explain,
new_observation = new_obs2,
type = "shap",
B = 10
)
rf_shap3 <-predict_parts(explainer = rf_explain,
new_observation = new_obs3,
type = "shap",
B = 10
)
plot(rf_shap1)

plot(rf_shap2)

plot(rf_shap3)

In all three of these shap plots the information that they are giving us is essentially the same as the break down profile, just with the added error bar.
- Construct a LIME graph (follow my code carefully). How close is each original prediction to the prediction from the local model? Interpret the result. You can also try using fewer or more variables in the local model than I used in the example.
set.seed(2)
model_type.dalex_explainer <- DALEXtra::model_type.dalex_explainer
predict_model.dalex_explainer <- DALEXtra::predict_model.dalex_explainer
lime_rf1 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs1 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
lime_rf1 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
This prediction is fairly close.
lime_rf2 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs2 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
lime_rf2 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
This prediction is also very close.
lime_rf3 <- predict_surrogate(explainer = rf_explain,
new_observation = new_obs3 %>%
select(-log_price),
n_features = 5,
n_permutations = 1000,
type = "lime")
lime_rf3 %>%
select(model_r2, model_prediction, prediction) %>%
distinct()
This prediction is also fairly close, but it is the furthest of the three.
plot(lime_rf1) +
labs(x = "Variable")

This is a little different from the first two, it has bathrooms as fairly important.
plot(lime_rf2) +
labs(x = "Variable")

This has both latitude and longitude as being important and positive, thus confirming that the location is good.
plot(lime_rf3) +
labs(x = "Variable")

This one is the most similar to the first two plots.
- Describe how you would use the interpretable machine learning tools we’ve learned (both local and global) in future machine learning projects? How does each of them help you?
Global interpretable ML is useful to identify larger trends and variable importance, including identifying redundant variables. Local ML are useful in comparing predictions to one another and seeing why the results varied.
SQL
You will use the airlines data from the SQL database that I used in the example in the tutorial. Be sure to include the chunk to connect to the database here. And, when you are finished, disconnect. You may need to reconnect throughout as it times out after a while.
Tasks:
- Create a SQL chunk and an equivalent R code chunk that does the following: for each airport (with its name, not code), and month find the total number of departing flights, the average distance of the flight, and the proportion of flights that arrived more than 20 minutes late. In the R code chunk, write this out to a dataset. (HINT: 1. start small! 2. you may want to do the R part first and use it to “cheat” into the SQL code).
con_air <- dbConnect(RMySQL::MySQL(),
dbname = "airlines",
host = "mdsr.cdc7tgkkqd0n.us-east-1.rds.amazonaws.com",
user = "mdsr_public",
password = "ImhsmflMDSwR")
dbListFields(con_air, "airports")
## [1] "faa" "name" "lat" "lon" "alt" "tz" "dst"
## [8] "city" "country"
dbListFields(con_air, "carriers")
## [1] "carrier" "name"
airport_summary <- tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(origin, month) %>%
summarise(total_departures = n(),
avg_dist = mean(distance),
prop_late_over20 = mean(arr_delay > 20)
) %>%
inner_join(tbl(con_air, "airports"),
by = c("origin" = "faa")) %>%
select(name, month, total_departures, avg_dist, prop_late_over20)
airport_summary
airports_query<-
tbl(con_air,sql("SELECT `origin`, `name`, `month`, `total_departures`, `avg_dist`, `prop_late_over20`
FROM (SELECT `LHS`.`origin` AS `origin`, `LHS`.`month` AS `month`, `LHS`.`total_departures` AS `total_departures`, `LHS`.`avg_dist` AS `avg_dist`, `LHS`.`prop_late_over20` AS `prop_late_over20`, `RHS`.`name` AS `name`, `RHS`.`lat` AS `lat`, `RHS`.`lon` AS `lon`, `RHS`.`alt` AS `alt`, `RHS`.`tz` AS `tz`, `RHS`.`dst` AS `dst`, `RHS`.`city` AS `city`, `RHS`.`country` AS `country`
FROM (SELECT `origin`, `month`, COUNT(*) AS `total_departures`, AVG(`distance`) AS `avg_dist`, AVG(`arr_delay` > 20.0) AS `prop_late_over20`
FROM `flights`
WHERE (`year` = 2017.0)
GROUP BY `origin`, `month`) `LHS`
INNER JOIN `airports` AS `RHS`
ON (`LHS`.`origin` = `RHS`.`faa`)
)`dbplyr_027`"))
show_query(airport_summary)
## <SQL>
## SELECT `origin`, `name`, `month`, `total_departures`, `avg_dist`, `prop_late_over20`
## FROM (SELECT `origin`, `month`, `total_departures`, `avg_dist`, `prop_late_over20`, `name`, `lat`, `lon`, `alt`, `tz`, `dst`, `city`, `country`
## FROM (SELECT `origin`, `month`, COUNT(*) AS `total_departures`, AVG(`distance`) AS `avg_dist`, AVG(`arr_delay` > 20.0) AS `prop_late_over20`
## FROM `flights`
## WHERE (`year` = 2017.0)
## GROUP BY `origin`, `month`) `LHS`
## INNER JOIN `airports` AS `RHS`
## ON (`LHS`.`origin` = `RHS`.`faa`)
## ) `q01`
airport_summary_collect <- collect(airport_summary)
- With the dataset you wrote out, create a graph that helps illustrate the “worst” airports in terms of late arrivals. You have some freedom in how you define worst and you may want to consider some of the other variables you computed. Do some theming to make your graph look glamorous (those of you who weren’t in my intro data science class this year may want to watch Will Chase’s Glamour of Graphics talk for inspiration).
airport_summary_collect2 <- airport_summary_collect %>%
mutate(month = as.factor(month)) %>%
filter(total_departures >= 20)
plot <- ggplot(airport_summary_collect2, aes(x = prop_late_over20,
y = avg_dist, color = name, shape = month)) +
geom_point() +
scale_x_continuous(expand = c(0,0),
labels = scales::percent) +
xlab("Proportion of Flights 20+ Minutes Late") +
ylab("Average Distance Travelled") +
theme(legend.position = "none")
ggplotly(plot)
- Although your graph was truly inspirational, you’ve been requested to “boil it down to a few numbers.” Some people just don’t appreciate all that effort you put in. And, you need to use the already summarized data that you already pulled in from SQL. Create a table with 6 or fewer rows and 3 or fewer columns that summarizes which airport is the “worst” in terms of late arrivals. Be careful with your calculations. You may consider using the
kable, kableExtra, or gt packages to make your table look truly spectacular.
airport_summary_collect2 %>%
filter(name %in% c("Nantucket Mem", "Bangor Intl", "Chippewa County International Airport", "Southwest Oregon Regional Airport", "Redding Muni", "Key Field")) %>%
group_by(name) %>%
summarise(`Proportion of Flights 20+ Minutes Late (Worst Month)` = max(prop_late_over20), `Average Distance Travelled` = mean(avg_dist))
- Come up with your own interesting question that data in the airlines database can help you answer. Write a SQL query and equivalent R code chunk to extract the data you need and create an elegant graph to help answer the question. Be sure to write down the question so it is clear.
What carrier is worst? Worst determined by proportion of flights canceled and average distance traveled (to account for the fact that shorter flights should get canceled less often).
cancelled <- tbl(con_air, "flights") %>%
filter(year == 2017) %>%
group_by(carrier, month) %>%
summarise(avg_dist = mean(distance),
prop_cancelled = mean(cancelled == 1),
total_departures = n()) %>%
inner_join(tbl(con_air, "carriers"),
by = c("carrier" = "carrier"))
cancelled
show_query(cancelled)
## <SQL>
## SELECT `LHS`.`carrier` AS `carrier`, `month`, `avg_dist`, `prop_cancelled`, `total_departures`, `name`
## FROM (SELECT `carrier`, `month`, AVG(`distance`) AS `avg_dist`, AVG(`cancelled` = 1.0) AS `prop_cancelled`, COUNT(*) AS `total_departures`
## FROM `flights`
## WHERE (`year` = 2017.0)
## GROUP BY `carrier`, `month`) `LHS`
## INNER JOIN `carriers` AS `RHS`
## ON (`LHS`.`carrier` = `RHS`.`carrier`)
cancelled_query <-
tbl(con_air,
sql("SELECT `LHS`.`carrier` AS `carrier`, `LHS`.`month` AS `month`, `LHS`.`avg_dist` AS `avg_dist`, `LHS`.`prop_cancelled` AS `prop_cancelled`, `LHS`.`total_departures` AS `total_departures`, `RHS`.`name` AS `name`
FROM (SELECT `carrier`, `month`, AVG(`distance`) AS `avg_dist`, AVG(`cancelled` = 1.0) AS `prop_cancelled`, COUNT(*) AS `total_departures`
FROM `flights`
WHERE (`year` = 2017.0)
GROUP BY `carrier`, `month`) `LHS`
INNER JOIN `carriers` AS `RHS`
ON (`LHS`.`carrier` = `RHS`.`carrier`)"
))
cancelled_collect <- collect(cancelled)
cancelled_collect2 <- cancelled_collect %>%
mutate(month = as.factor(month))
plot <- ggplot(cancelled_collect2, aes(x = prop_cancelled,
y = avg_dist, color = name, shape = month)) +
geom_point() +
scale_x_continuous(expand = c(0,0),
labels = scales::percent) +
xlab("Proportion of Flights Cancelled") +
ylab("Average Distance Travelled") +
theme(legend.position = "none")
ggplotly(plot)
Spirit, Express, and Jet Blue are the worst airlines.
dbDisconnect(con_air)
## [1] TRUE
Function Friday
If you need to revisit the material, it is posted on the moodle page. I’ve tried to add all the necessary libraries to the top, but I may have missed something.
geom_sf() tasks:
Using the example from class that we presented as a baseline (or your own if you really want to be ambitious), try to add the following components to the map of the contiguous United States:
- Change the color scheme of the map from the default blue (one option could be viridis).
- Add a dot (or any symbol you want) to the centroid of each state.
- Add a layer onto the map with the counties.
- Change the coordinates of the map to zoom in on your favorite state.
states <- st_as_sf(maps::map("state",
plot = FALSE,
fill = TRUE))
head(states)
ggplot(data = states) +
geom_sf(fill = NA) +
coord_sf(xlim = c(-127, -63), ylim = c(24, 51), expand = FALSE)

states <- states %>%
mutate(area = as.numeric(st_area(states)))
ggplot(data = states) +
geom_sf(aes(fill = area)) +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE)

ggplot(data = states) +
geom_sf(aes(fill = area)) +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE) +
scale_fill_viridis_b()

ggplot(data = states) +
geom_sf(aes(fill = area)) +
stat_sf_coordinates() +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE)

counties <- st_as_sf(map("county", plot = FALSE, fill = TRUE))
counties$area <- as.numeric(st_area(counties))
ggplot(data = states) +
geom_sf(data = counties, aes(fill = area)) +
coord_sf(xlim = c(-127, -63),
ylim = c(24, 51),
expand = FALSE) +
scale_fill_viridis_b()

ggplot(data = states) +
geom_sf(data = counties, aes(fill = area)) +
coord_sf(xlim = c(-124.21, -113.19),
ylim = c(32.53, 41.998),
expand = FALSE) +
scale_fill_viridis_b()

Hint: https://www.r-spatial.org/r/2018/10/25/ggplot2-sf-2.html is a useful reference for some of the questions
tidytext tasks:
Now you will try using tidytext on a new dataset about Russian Troll tweets.
Read about the data
These are tweets from Twitter handles that are connected to the Internet Research Agency (IRA), a Russian “troll factory.” The majority of these tweets were posted from 2015-2017, but the datasets encompass tweets from February 2012 to May 2018.
Three of the main categories of troll tweet that we will be focusing on are Left Trolls, Right Trolls, and News Feed. Left Trolls usually pretend to be BLM activists, aiming to divide the democratic party (in this context, being pro-Bernie so that votes are taken away from Hillary). Right trolls imitate Trump supporters, and News Feed handles are “local news aggregators,” typically linking to legitimate news.
For our upcoming analyses, some important variables are:
- author (handle sending the tweet)
- content (text of the tweet)
- language (language of the tweet)
- publish_date (date and time the tweet was sent)
Variable documentation can be found on Github and a more detailed description of the dataset can be found in this fivethirtyeight article.
Because there are 12 datasets containing 2,973,371 tweets sent by 2,848 Twitter handles in total, we will be using three of these datasets (one from a Right troll, one from a Left troll, and one from a News Feed account).
- Read in Troll Tweets Dataset - this takes a while. You can cache it so you don’t need to read it in again each time you knit. Be sure to remove the
eval=FALSE!!!!
troll_tweets <- read_csv("https://raw.githubusercontent.com/fivethirtyeight/russian-troll-tweets/master/IRAhandle_tweets_12.csv")
- Basic Data Cleaning and Exploration
- Remove rows where the tweet was in a language other than English
- Report the dimensions of the dataset
- Create two or three basic exploratory plots of the data (ex. plot of the different locations from which tweets were posted, plot of the account category of a tweet)
troll_tweets2 <- troll_tweets %>%
filter(language == "English")
troll_tweets2 %>%
dim()
## [1] 175966 21
troll_tweets2 %>%
ggplot(aes(x = account_type)) +
geom_bar()

troll_tweets2 %>%
ggplot(aes(x = retweet)) +
geom_bar()

- Unnest Tokens
We want each row to represent a word from a tweet, rather than an entire tweet. Be sure to remove the eval=FALSE!!!!
troll_tweets_untoken <- troll_tweets2 %>%
unnest_tokens(word,content)
troll_tweets_untoken
- Remove stopwords. Be sure to remove the
eval=FALSE!!!!
#get rid of stopwords (the, and, etc.)
troll_tweets_cleaned <- troll_tweets_untoken %>%
anti_join(stop_words)
Take a look at the troll_tweets_cleaned dataset. Are there any other words/letters/numbers that we want to eliminate that weren’t taken care of by stop_words? Be sure to remove the eval=FALSE!!!!
#get rid of http, https, t.co, rt, amp, single number digits, and singular letters
troll_tweets_cleaned <- troll_tweets_cleaned %>%
filter(word != 'https') %>%
filter(word != "t.co") %>%
filter(word != "http") %>%
filter(word != "trump") %>%
filter(word != "trump's")
- Look at a subset of the tweets to see how often the top words appear.
troll_tweets_small <- troll_tweets_cleaned %>%
count(word) %>%
slice_max(order_by = n, n = 50) # 50 most occurring words
# visualize the number of times the 50 top words appear
ggplot(troll_tweets_small,
aes(y = fct_reorder(word,n), x = n)) +
geom_col()

- Sentiment Analysis
- Get the sentiments using the “bing” parameter (which classifies words into “positive” or “negative”)
- Report how many positive and negative words there are in the dataset. Are there more positive or negative words, and why do you think this might be?
Be sure to remove the eval=FALSE!!!!
# look at sentiment
sentiments <- get_sentiments("bing")
# assign a sentiment to each word that has one associated
troll_tweets_sentiment <- troll_tweets_cleaned %>%
inner_join(sentiments)
# count the sentiments
troll_tweets_sentiment %>%
group_by(sentiment) %>%
summarise(count = n())
- Using the troll_tweets_small dataset, make a wordcloud:
- That is sized by the number of times that a word appears in the tweets
- That is colored by sentiment (positive or negative)
Be sure to remove the eval=FALSE!!!!
# make a wordcloud where the size of the word is based on the number of times the word appears across the tweets
troll_tweets_small %>%
with(wordcloud(word, n, max.words = 50))

# make a wordcloud colored by sentiment
troll_tweets_sentiment %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("red","green"),
max.words = 50)

Are there any words whose categorization as “positive” or “negative” surprised you?
It was categorizing trump as positive, it was probably because the noun or verb form of the word and not trumps name, so I removed it due to context.
Projects
Read the project description on the moodle page. Talk to your group members about potential topics.
Task:
Write a short paragraph about ideas you have. If you already have some data sources in mind, you can link to those, but I’m more concerned with you having a topic that you’re interested in investigating right now.
We will probably do something with soccer, which is a little bit outside of all of our comfort zones as those of us who primarily work with sports analysis focus on baseball. We were thinking of making a model that predicts FIFA Ultimate Team’s team of the season players based on real world stats and team performance. Or predicting soccer results or something along those lines. We would need to learn some data scraping techniques so that would be a bridge we would have to cross if we go forward with the project on these topics.
“Undoing” bias
Task:
Read this tweet thread by Deb Raji who you may remember from the Coded Bias film. Write a short paragraph that discusses at least one of the misconceptions.
The misconception that most surprised me was that race and gender are the least obvious biases to detect. It makes sense after hearing that however because there are so many models being used now that have these issues that aren’t detected. Just because those data points aren’t in the data people may believe that the algorithm cannot predict off those attributes, but proxies exist as Raji points out. It makes sense that those attributes not in the data are the hardest to find the biases from, as the proxies do the work.
LS0tCnRpdGxlOiAiSG9tZXdvcmsgMyBBRFIiCmF1dGhvcjogIk1pY2hhZWwgSGVsdG9uIgpkYXRlOiAiNC8xMi8yMDIxIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0V9CiMgU0VFIG1vZGVsZGF0YSBwYWNrYWdlIGZvciBuZXcgZGF0YXNldHMKbGlicmFyeSh0aWR5dmVyc2UpICAgICAgICAgIyBmb3IgZ3JhcGhpbmcgYW5kIGRhdGEgY2xlYW5pbmcKbGlicmFyeSh0aWR5bW9kZWxzKSAgICAgICAgIyBmb3IgbW9kZWxpbmcKbGlicmFyeShzdGFja3MpICAgICAgICAgICAgIyBmb3Igc3RhY2tpbmcgbW9kZWxzCmxpYnJhcnkobmFuaWFyKSAgICAgICAgICAgICMgZm9yIGV4YW1pbmluZyBtaXNzaW5nIHZhbHVlcyAoTkFzKQpsaWJyYXJ5KGx1YnJpZGF0ZSkgICAgICAgICAjIGZvciBkYXRlIG1hbmlwdWxhdGlvbgpsaWJyYXJ5KG1vZGVybmRpdmUpICAgICAgICAjIGZvciBLaW5nIENvdW50eSBob3VzaW5nIGRhdGEKbGlicmFyeShEQUxFWCkgICAgICAgICAgICAgIyBmb3IgbW9kZWwgaW50ZXJwcmV0YXRpb24gIApsaWJyYXJ5KERBTEVYdHJhKSAgICAgICAgICAjIGZvciBleHRlbnNpb24gb2YgREFMRVgKbGlicmFyeShwYXRjaHdvcmspICAgICAgICAgIyBmb3IgY29tYmluaW5nIHBsb3RzIG5pY2VseQpsaWJyYXJ5KGRicGx5cikgICAgICAgICAgICAjIGZvciBTUUwgcXVlcnkgImNoZWF0aW5nIiAtIHBhcnQgb2YgdGlkeXZlcnNlIGJ1dCBuZWVkcyB0byBiZSBsb2FkZWQgc2VwYXJhdGVseQpsaWJyYXJ5KG1kc3IpICAgICAgICAgICAgICAjIGZvciBhY2Nlc3Npbmcgc29tZSBkYXRhYmFzZXMgLSBnb2VzIHdpdGggTW9kZXJuIERhdGEgU2NpZW5jZSB3aXRoIFIgdGV4dGJvb2sKbGlicmFyeShSTXlTUUwpICAgICAgICAgICAgIyBmb3IgYWNjZXNzaW5nIE15U1FMIGRhdGFiYXNlcwpsaWJyYXJ5KFJTUUxpdGUpICAgICAgICAgICAjIGZvciBhY2Nlc3NpbmcgU1FMaXRlIGRhdGFiYXNlcwojbWFwcGluZwpsaWJyYXJ5KG1hcHMpICAgICAgICAgICAgICAjIGZvciBidWlsdC1pbiBtYXBzCmxpYnJhcnkoc2YpICAgICAgICAgICAgICAgICMgZm9yIG1ha2luZyBtYXBzIHVzaW5nIGdlb21fc2YKbGlicmFyeShnZ3RoZW1lcykgICAgICAgICAgIyBMaXNhIGFkZGVkIC0gSSBsaWtlIHRoZW1lX21hcCgpIGZvciBtYXBzIDopCiN0aWR5dGV4dApsaWJyYXJ5KHRpZHl0ZXh0KSAgICAgICAgICAjIGZvciB0ZXh0IGFuYWx5c2lzLCB0aGUgdGlkeSB3YXkhCmxpYnJhcnkodGV4dGRhdGEpICAgICAgICAgIApsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHdvcmRjbG91ZCkgICAgICAgICAjIGZvciB3b3JkY2xvdWQKbGlicmFyeShzdG9wd29yZHMpCmxpYnJhcnkocGxvdGx5KQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjIExpc2EncyBmYXZvcml0ZSB0aGVtZQpgYGAKCldoZW4geW91IGZpbmlzaCB0aGUgYXNzaWdubWVudCwgcmVtb3ZlIHRoZSBgI2AgZnJvbSB0aGUgb3B0aW9ucyBjaHVuayBhdCB0aGUgdG9wLCBzbyB0aGF0IG1lc3NhZ2VzIGFuZCB3YXJuaW5ncyBhcmVuJ3QgcHJpbnRlZC4gSWYgeW91IGFyZSBnZXR0aW5nIGVycm9ycyBpbiB5b3VyIGNvZGUsIGFkZCBgZXJyb3IgPSBUUlVFYCBzbyB0aGF0IHRoZSBmaWxlIGtuaXRzLiBJIHdvdWxkIHJlY29tbWVuZCBub3QgcmVtb3ZpbmcgdGhlIGAjYCB1bnRpbCB5b3UgYXJlIGNvbXBsZXRlbHkgZmluaXNoZWQuCgojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgCgpGcm9tIG5vdyBvbiwgR2l0SHViIHNob3VsZCBiZSBwYXJ0IG9mIHlvdXIgcm91dGluZSB3aGVuIGRvaW5nIGFzc2lnbm1lbnRzLiBJIHJlY29tbWVuZCBtYWtpbmcgaXQgcGFydCBvZiB5b3VyIHByb2Nlc3MgYW55dGltZSB5b3UgYXJlIHdvcmtpbmcgaW4gUiwgYnV0IEknbGwgbWFrZSB5b3Ugc2hvdyBpdCdzIHBhcnQgb2YgeW91ciBwcm9jZXNzIGZvciBhc3NpZ25tZW50cy4KCioqVGFzayoqOiBXaGVuIHlvdSBhcmUgZmluaXNoZWQgd2l0aCB0aGUgYXNzaWdubWVudCwgcG9zdCBhIGxpbmsgYmVsb3cgdG8gdGhlIEdpdEh1YiByZXBvIGZvciB0aGUgYXNzaWdubWVudC4gSWYgeW91IHdhbnQgdG8gcG9zdCBpdCB0byB5b3VyIHBlcnNvbmFsIHdlYnNpdGUsIHRoYXQncyBvayAobm90IHJlcXVpcmVkKS4gTWFrZSBzdXJlIHRoZSBsaW5rIGdvZXMgdG8gYSBzcG90IGluIHRoZSByZXBvIHdoZXJlIEkgY2FuIGVhc2lseSBmaW5kIHRoaXMgYXNzaWdubWVudC4gRm9yIGV4YW1wbGUsIGlmIHlvdSBoYXZlIGEgd2Vic2l0ZSB3aXRoIGEgYmxvZyBhbmQgcG9zdCB0aGUgYXNzaWdubWVudCBhcyBhIGJsb2cgcG9zdCwgbGluayB0byB0aGUgcG9zdCdzIGZvbGRlciBpbiB0aGUgcmVwby4gQXMgYW4gZXhhbXBsZSwgSSd2ZSBsaW5rZWQgdG8gbXkgR2l0SHViIHN0YWNraW5nIG1hdGVyaWFsIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vbGxlbmR3YXkvYWRzX3dlYnNpdGUvdHJlZS9tYXN0ZXIvX3Bvc3RzLzIwMjEtMDMtMjItc3RhY2tpbmcpLgpcCltHaXRodWIgUmVwb10oaHR0cHM6Ly9naXRodWIuY29tL21pY2hhZWxoZXRsby9Ib21ld29yazNfQURSKQoKCiMjIExvY2FsIEludGVycHJldGFibGUgTWFjaGluZSBMZWFybmluZwoKWW91IGFyZSBnb2luZyB0byB1c2UgdGhlIEtpbmcgQ291bnR5IGhvdXNlIGRhdGEgYW5kIHRoZSBzYW1lIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdG8gcHJlZGljdCBgbG9nX3ByaWNlYCB0aGF0IEkgdXNlZCBpbiB0aGUgW3R1dG9yaWFsXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0zMS1pbWxsb2NhbC8pLgoKKipUYXNrczoqKgoKYGBge3J9CmRhdGEoImhvdXNlX3ByaWNlcyIpCiMgQ3JlYXRlIGxvZ19wcmljZSBhbmQgZHJvcCBwcmljZSB2YXJpYWJsZQpob3VzZV9wcmljZXMgPC0gaG91c2VfcHJpY2VzICU+JSAKICBtdXRhdGUobG9nX3ByaWNlID0gbG9nKHByaWNlLCBiYXNlID0gMTApKSAlPiUgCiAgIyBtYWtlIGFsbCBpbnRlZ2VycyBudW1lcmljIC4uLiBmaXhlcyBwcmVkaWN0aW9uIHByb2JsZW0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmludGVnZXIpLCBhcy5udW1lcmljKSkgJT4lIAogIHNlbGVjdCgtcHJpY2UpCmBgYAoKYGBge3J9CmhvdXNlX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoaG91c2VfcHJpY2VzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9wID0gLjc1KQpob3VzZV90cmFpbmluZyA8LSB0cmFpbmluZyhob3VzZV9zcGxpdCkKaG91c2VfdGVzdGluZyA8LSB0ZXN0aW5nKGhvdXNlX3NwbGl0KQpgYGAKCgoKYGBge3J9CnJhbmdlcl9yZWNpcGUgPC0gCiAgcmVjaXBlKGZvcm11bGEgPSBsb2dfcHJpY2UgfiAuLCAKICAgICAgICAgZGF0YSA9IGhvdXNlX3RyYWluaW5nKSAlPiUgCiAgc3RlcF9kYXRlKGRhdGUsIAogICAgICAgICAgICBmZWF0dXJlcyA9ICJtb250aCIpICU+JSAKICAjIE1ha2UgdGhlc2UgZXZhbHVhdGl2ZSB2YXJpYWJsZXMsIG5vdCBpbmNsdWRlZCBpbiBtb2RlbGluZwogIHVwZGF0ZV9yb2xlKGFsbF9vZihjKCJpZCIsCiAgICAgICAgICAgICAgICAgICAgICAgImRhdGUiKSksCiAgICAgICAgICAgICAgbmV3X3JvbGUgPSAiZXZhbHVhdGl2ZSIpCiNkZWZpbmUgbW9kZWwKcmFuZ2VyX3NwZWMgPC0gCiAgcmFuZF9mb3Jlc3QobXRyeSA9IDYsIAogICAgICAgICAgICAgIG1pbl9uID0gMTAsIAogICAgICAgICAgICAgIHRyZWVzID0gMjAwKSAlPiUgCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKSAlPiUgCiAgc2V0X2VuZ2luZSgicmFuZ2VyIikKI2NyZWF0ZSB3b3JrZmxvdwpyYW5nZXJfd29ya2Zsb3cgPC0gCiAgd29ya2Zsb3coKSAlPiUgCiAgYWRkX3JlY2lwZShyYW5nZXJfcmVjaXBlKSAlPiUgCiAgYWRkX21vZGVsKHJhbmdlcl9zcGVjKSAKI2ZpdCB0aGUgbW9kZWwKc2V0LnNlZWQoNzEyKSAjIGZvciByZXByb2R1Y2liaWxpdHkgLSByYW5kb20gc2FtcGxpbmcgaW4gcmFuZG9tIGZvcmVzdCBjaG9vc2luZyBudW1iZXIgb2YgdmFyaWFibGVzCnJhbmdlcl9maXQgPC0gcmFuZ2VyX3dvcmtmbG93ICU+JSAKICBmaXQoaG91c2VfdHJhaW5pbmcpCmBgYAoKYGBge3J9CnJmX2V4cGxhaW4gPC0gCiAgZXhwbGFpbl90aWR5bW9kZWxzKAogICAgbW9kZWwgPSByYW5nZXJfZml0LAogICAgZGF0YSA9IGhvdXNlX3RyYWluaW5nICU+JSBzZWxlY3QoLWxvZ19wcmljZSksIAogICAgeSA9IGhvdXNlX3RyYWluaW5nICU+JSAgcHVsbChsb2dfcHJpY2UpLAogICAgbGFiZWwgPSAicmYiCiAgKQpgYGAKCmBgYHtyfQpuZXdfb2JzMSA8LSBob3VzZV90ZXN0aW5nICU+JSBzbGljZSgzMDYpIApuZXdfb2JzMiA8LSBob3VzZV90ZXN0aW5nICU+JSBzbGljZSg1MzY2KSAKbmV3X29iczMgPC0gaG91c2VfdGVzdGluZyAlPiUgc2xpY2UoMTApIApgYGAKCgoKCjEuIENob29zZSAzIG5ldyBvYnNlcnZhdGlvbnMgYW5kIGRvIHRoZSBmb2xsb3dpbmcgZm9yIGVhY2ggb2JzZXJ2YXRpb246ICAKICAtIENvbnN0cnVjdCBhIGJyZWFrLWRvd24gcGxvdCB1c2luZyB0aGUgZGVmYXVsdCBvcmRlcmluZy4gSW50ZXJwcmV0IHRoZSByZXN1bHRpbmcgZ3JhcGguIFdoaWNoIHZhcmlhYmxlcyBjb250cmlidXRlIG1vc3QgdG8gZWFjaCBvYnNlcnZhdGlvbidzIHByZWRpY3Rpb24/ICAKICAKYGBge3J9CnBwX3JmMSA8LSBwcmVkaWN0X3BhcnRzKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29iczEsCiAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJicmVha19kb3duIikKcHBfcmYyIDwtIHByZWRpY3RfcGFydHMoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwKICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMiwKICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImJyZWFrX2Rvd24iKQpwcF9yZjMgPC0gcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMzLAogICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiYnJlYWtfZG93biIpCmBgYAoKCmBgYHtyfQpwbG90KHBwX3JmMSkKYGBgClwKKipUaGUgeWVhciB0aGUgaG91c2Ugd2FzIGJ1aWx0LCB0aGUgc2l6ZSBvZiB0aGUgbGl2aW5nIHNwYWNlLCBhbmQgdGhlIGxvY2F0aW9uIHNlZW0gdG8gYmUgdGhlIG1vc3QgaW1wb3J0YW50IGZvciB0aGlzIG9ic2VydmF0aW9uLiBUaGV5IGFyZSBhbGwgcG9zaXRpdmUgZm9yIHRoZSBwcmVkaWN0aW9uIG9mIHByaWNlLioqCmBgYHtyfQpwbG90KHBwX3JmMikKYGBgClwKKipGb3IgdGhpcyBvYnNlcnZhdGlvbiBpdCBzZWVtcyB0byBiZSBzaW1pbGFyIHZhcmlhYmxlcyB0aGF0IGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQsIGJ1dCBvbmx5IHRoZSBsb2NhdGlvbiBpcyBwb3NpdGl2ZS4qKgoKYGBge3J9CnBsb3QocHBfcmYzKQpgYGAKXAoqKkZvciB0aGlzIG9ic2VydmF0aW9uIHRoZSBzYW1lIHZhcmlhYmxlcyBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IGFuZCB0aGV5IGFyZSBhbGwgc3Ryb25nbHkgcG9zaXRpdmUuKioKCgogIAogIC0gQ29uc3RydWN0IGEgU0hBUCBncmFwaCBhbmQgaW50ZXJwcmV0IGl0LiBEb2VzIGl0IHRlbGwgYSBzaW1pbGFyIHN0b3J5IHRvIHRoZSBicmVhay1kb3duIHBsb3Q/ICAKICAKYGBge3J9CnJmX3NoYXAxIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMSwKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwIAopCnJmX3NoYXAyIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMiwKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwIAopCnJmX3NoYXAzIDwtcHJlZGljdF9wYXJ0cyhleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMywKICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJzaGFwIiwKICAgICAgICAgICAgICAgICAgICAgICAgQiA9IDEwIAopCmBgYAoKYGBge3J9CnBsb3QocmZfc2hhcDEpCmBgYAoKCmBgYHtyfQpwbG90KHJmX3NoYXAyKQpgYGAKCmBgYHtyfQpwbG90KHJmX3NoYXAzKQpgYGAKXAoqKkluIGFsbCB0aHJlZSBvZiB0aGVzZSBzaGFwIHBsb3RzIHRoZSBpbmZvcm1hdGlvbiB0aGF0IHRoZXkgYXJlIGdpdmluZyB1cyBpcyBlc3NlbnRpYWxseSB0aGUgc2FtZSBhcyB0aGUgYnJlYWsgZG93biBwcm9maWxlLCBqdXN0IHdpdGggdGhlIGFkZGVkIGVycm9yIGJhci4qKgoKICAKICAtIENvbnN0cnVjdCBhIExJTUUgZ3JhcGggKGZvbGxvdyBteSBjb2RlIGNhcmVmdWxseSkuIEhvdyBjbG9zZSBpcyBlYWNoIG9yaWdpbmFsIHByZWRpY3Rpb24gdG8gdGhlIHByZWRpY3Rpb24gZnJvbSB0aGUgbG9jYWwgbW9kZWw/IEludGVycHJldCB0aGUgcmVzdWx0LiBZb3UgY2FuIGFsc28gdHJ5IHVzaW5nIGZld2VyIG9yIG1vcmUgdmFyaWFibGVzIGluIHRoZSBsb2NhbCBtb2RlbCB0aGFuIEkgdXNlZCBpbiB0aGUgZXhhbXBsZS4gIAogIApgYGB7cn0Kc2V0LnNlZWQoMikKbW9kZWxfdHlwZS5kYWxleF9leHBsYWluZXIgPC0gREFMRVh0cmE6Om1vZGVsX3R5cGUuZGFsZXhfZXhwbGFpbmVyCnByZWRpY3RfbW9kZWwuZGFsZXhfZXhwbGFpbmVyIDwtIERBTEVYdHJhOjpwcmVkaWN0X21vZGVsLmRhbGV4X2V4cGxhaW5lcgpsaW1lX3JmMSA8LSBwcmVkaWN0X3N1cnJvZ2F0ZShleHBsYWluZXIgPSByZl9leHBsYWluLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19vYnNlcnZhdGlvbiA9IG5ld19vYnMxICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KC1sb2dfcHJpY2UpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2ZlYXR1cmVzID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3Blcm11dGF0aW9ucyA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJsaW1lIikKbGltZV9yZjEgJT4lIAogIHNlbGVjdChtb2RlbF9yMiwgbW9kZWxfcHJlZGljdGlvbiwgcHJlZGljdGlvbikgJT4lIAogIGRpc3RpbmN0KCkKYGBgClwKKipUaGlzIHByZWRpY3Rpb24gaXMgZmFpcmx5IGNsb3NlLioqCgpgYGB7cn0KbGltZV9yZjIgPC0gcHJlZGljdF9zdXJyb2dhdGUoZXhwbGFpbmVyID0gcmZfZXhwbGFpbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfb2JzZXJ2YXRpb24gPSBuZXdfb2JzMiAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtbG9nX3ByaWNlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9mZWF0dXJlcyA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9wZXJtdXRhdGlvbnMgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAibGltZSIpCmxpbWVfcmYyICU+JSAKICBzZWxlY3QobW9kZWxfcjIsIG1vZGVsX3ByZWRpY3Rpb24sIHByZWRpY3Rpb24pICU+JSAKICBkaXN0aW5jdCgpCmBgYApcCioqVGhpcyBwcmVkaWN0aW9uIGlzIGFsc28gdmVyeSBjbG9zZS4qKgoKYGBge3J9CmxpbWVfcmYzIDwtIHByZWRpY3Rfc3Vycm9nYXRlKGV4cGxhaW5lciA9IHJmX2V4cGxhaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3X29ic2VydmF0aW9uID0gbmV3X29iczMgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWxvZ19wcmljZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fZmVhdHVyZXMgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fcGVybXV0YXRpb25zID0gMTAwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gImxpbWUiKQpsaW1lX3JmMyAlPiUgCiAgc2VsZWN0KG1vZGVsX3IyLCBtb2RlbF9wcmVkaWN0aW9uLCBwcmVkaWN0aW9uKSAlPiUgCiAgZGlzdGluY3QoKQpgYGAKClwKKipUaGlzIHByZWRpY3Rpb24gaXMgYWxzbyBmYWlybHkgY2xvc2UsIGJ1dCBpdCBpcyB0aGUgZnVydGhlc3Qgb2YgdGhlIHRocmVlLioqCmBgYHtyfQpwbG90KGxpbWVfcmYxKSArCiAgbGFicyh4ID0gIlZhcmlhYmxlIikKYGBgClwKKipUaGlzIGlzIGEgbGl0dGxlIGRpZmZlcmVudCBmcm9tIHRoZSBmaXJzdCB0d28sIGl0IGhhcyBiYXRocm9vbXMgYXMgZmFpcmx5IGltcG9ydGFudC4qKgpgYGB7cn0KcGxvdChsaW1lX3JmMikgKwogIGxhYnMoeCA9ICJWYXJpYWJsZSIpCmBgYApcCioqVGhpcyBoYXMgYm90aCBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGFzIGJlaW5nIGltcG9ydGFudCBhbmQgcG9zaXRpdmUsIHRodXMgY29uZmlybWluZyB0aGF0IHRoZSBsb2NhdGlvbiBpcyBnb29kLioqCgpgYGB7cn0KcGxvdChsaW1lX3JmMykgKwogIGxhYnMoeCA9ICJWYXJpYWJsZSIpCmBgYApcCiAqKlRoaXMgb25lIGlzIHRoZSBtb3N0IHNpbWlsYXIgdG8gdGhlIGZpcnN0IHR3byBwbG90cy4qKgoKCjIuIERlc2NyaWJlIGhvdyB5b3Ugd291bGQgdXNlIHRoZSBpbnRlcnByZXRhYmxlIG1hY2hpbmUgbGVhcm5pbmcgdG9vbHMgd2UndmUgbGVhcm5lZCAoYm90aCBsb2NhbCBhbmQgZ2xvYmFsKSBpbiBmdXR1cmUgbWFjaGluZSBsZWFybmluZyBwcm9qZWN0cz8gSG93IGRvZXMgZWFjaCBvZiB0aGVtIGhlbHAgeW91PwoKXAoqKkdsb2JhbCBpbnRlcnByZXRhYmxlIE1MIGlzIHVzZWZ1bCB0byBpZGVudGlmeSBsYXJnZXIgdHJlbmRzIGFuZCB2YXJpYWJsZSBpbXBvcnRhbmNlLCBpbmNsdWRpbmcgaWRlbnRpZnlpbmcgcmVkdW5kYW50IHZhcmlhYmxlcy4gIExvY2FsIE1MIGFyZSB1c2VmdWwgaW4gY29tcGFyaW5nIHByZWRpY3Rpb25zIHRvIG9uZSBhbm90aGVyIGFuZCBzZWVpbmcgd2h5IHRoZSByZXN1bHRzIHZhcmllZC4gKioKCiMjIFNRTAoKWW91IHdpbGwgdXNlIHRoZSBgYWlybGluZXNgIGRhdGEgZnJvbSB0aGUgU1FMIGRhdGFiYXNlIHRoYXQgSSB1c2VkIGluIHRoZSBleGFtcGxlIGluIHRoZSBbdHV0b3JpYWxdKGh0dHBzOi8vYWR2YW5jZWQtZHMtaW4tci5uZXRsaWZ5LmFwcC9wb3N0cy8yMDIxLTAzLTI5LXNxbGluci8pLiBCZSBzdXJlIHRvIGluY2x1ZGUgdGhlIGNodW5rIHRvIGNvbm5lY3QgdG8gdGhlIGRhdGFiYXNlIGhlcmUuIEFuZCwgd2hlbiB5b3UgYXJlIGZpbmlzaGVkLCBkaXNjb25uZWN0LiBZb3UgbWF5IG5lZWQgdG8gcmVjb25uZWN0IHRocm91Z2hvdXQgYXMgaXQgdGltZXMgb3V0IGFmdGVyIGEgd2hpbGUuCgoqKlRhc2tzKio6CgoxLiBDcmVhdGUgYSBTUUwgY2h1bmsgYW5kIGFuIGVxdWl2YWxlbnQgUiBjb2RlIGNodW5rIHRoYXQgZG9lcyB0aGUgZm9sbG93aW5nOiBmb3IgZWFjaCBhaXJwb3J0ICh3aXRoIGl0cyBuYW1lLCBub3QgY29kZSksIGFuZCBtb250aCBmaW5kIHRoZSB0b3RhbCBudW1iZXIgb2YgZGVwYXJ0aW5nIGZsaWdodHMsIHRoZSBhdmVyYWdlIGRpc3RhbmNlIG9mIHRoZSBmbGlnaHQsIGFuZCB0aGUgcHJvcG9ydGlvbiBvZiBmbGlnaHRzIHRoYXQgYXJyaXZlZCBtb3JlIHRoYW4gMjAgbWludXRlcyBsYXRlLiBJbiB0aGUgUiBjb2RlIGNodW5rLCB3cml0ZSB0aGlzIG91dCB0byBhIGRhdGFzZXQuIChISU5UOiAxLiBzdGFydCBzbWFsbCEgMi4geW91IG1heSB3YW50IHRvIGRvIHRoZSBSIHBhcnQgZmlyc3QgYW5kIHVzZSBpdCB0byAiY2hlYXQiIGludG8gdGhlIFNRTCBjb2RlKS4gIAoKCmBgYHtyfQpjb25fYWlyIDwtIGRiQ29ubmVjdChSTXlTUUw6Ok15U1FMKCksIAogICAgICAgICAgICAgICAgICAgICBkYm5hbWUgPSAiYWlybGluZXMiLCAKICAgICAgICAgICAgICAgICAgICAgaG9zdCA9ICJtZHNyLmNkYzd0Z2trcWQwbi51cy1lYXN0LTEucmRzLmFtYXpvbmF3cy5jb20iLCAKICAgICAgICAgICAgICAgICAgICAgdXNlciA9ICJtZHNyX3B1YmxpYyIsIAogICAgICAgICAgICAgICAgICAgICBwYXNzd29yZCA9ICJJbWhzbWZsTURTd1IiKQpgYGAKCgpgYGB7cn0KZGJMaXN0RmllbGRzKGNvbl9haXIsICJhaXJwb3J0cyIpCmBgYAoKYGBge3J9CmRiTGlzdEZpZWxkcyhjb25fYWlyLCAiY2FycmllcnMiKQpgYGAKCgoKYGBge3J9CmFpcnBvcnRfc3VtbWFyeSA8LSB0YmwoY29uX2FpciwgImZsaWdodHMiKSAlPiUgCiAgZmlsdGVyKHllYXIgPT0gMjAxNykgJT4lIAogIGdyb3VwX2J5KG9yaWdpbiwgbW9udGgpICU+JQogIHN1bW1hcmlzZSh0b3RhbF9kZXBhcnR1cmVzID0gbigpLAogICAgICAgICAgICBhdmdfZGlzdCA9IG1lYW4oZGlzdGFuY2UpLAogICAgICAgICAgICBwcm9wX2xhdGVfb3ZlcjIwID0gbWVhbihhcnJfZGVsYXkgPiAyMCkKICAgICAgICAgICAgKSAlPiUgCiAgaW5uZXJfam9pbih0YmwoY29uX2FpciwgImFpcnBvcnRzIiksCiAgICAgICAgICAgICBieSA9IGMoIm9yaWdpbiIgPSAiZmFhIikpICU+JSAKICBzZWxlY3QobmFtZSwgbW9udGgsIHRvdGFsX2RlcGFydHVyZXMsIGF2Z19kaXN0LCBwcm9wX2xhdGVfb3ZlcjIwKQphaXJwb3J0X3N1bW1hcnkKICAKYGBgCgpgYGB7cn0KYWlycG9ydHNfcXVlcnk8LSAKdGJsKGNvbl9haXIsc3FsKCJTRUxFQ1QgYG9yaWdpbmAsIGBuYW1lYCwgYG1vbnRoYCwgYHRvdGFsX2RlcGFydHVyZXNgLCBgYXZnX2Rpc3RgLCBgcHJvcF9sYXRlX292ZXIyMGAKRlJPTSAoU0VMRUNUIGBMSFNgLmBvcmlnaW5gIEFTIGBvcmlnaW5gLCBgTEhTYC5gbW9udGhgIEFTIGBtb250aGAsIGBMSFNgLmB0b3RhbF9kZXBhcnR1cmVzYCBBUyBgdG90YWxfZGVwYXJ0dXJlc2AsIGBMSFNgLmBhdmdfZGlzdGAgQVMgYGF2Z19kaXN0YCwgYExIU2AuYHByb3BfbGF0ZV9vdmVyMjBgIEFTIGBwcm9wX2xhdGVfb3ZlcjIwYCwgYFJIU2AuYG5hbWVgIEFTIGBuYW1lYCwgYFJIU2AuYGxhdGAgQVMgYGxhdGAsIGBSSFNgLmBsb25gIEFTIGBsb25gLCBgUkhTYC5gYWx0YCBBUyBgYWx0YCwgYFJIU2AuYHR6YCBBUyBgdHpgLCBgUkhTYC5gZHN0YCBBUyBgZHN0YCwgYFJIU2AuYGNpdHlgIEFTIGBjaXR5YCwgYFJIU2AuYGNvdW50cnlgIEFTIGBjb3VudHJ5YApGUk9NIChTRUxFQ1QgYG9yaWdpbmAsIGBtb250aGAsIENPVU5UKCopIEFTIGB0b3RhbF9kZXBhcnR1cmVzYCwgQVZHKGBkaXN0YW5jZWApIEFTIGBhdmdfZGlzdGAsIEFWRyhgYXJyX2RlbGF5YCA+IDIwLjApIEFTIGBwcm9wX2xhdGVfb3ZlcjIwYApGUk9NIGBmbGlnaHRzYApXSEVSRSAoYHllYXJgID0gMjAxNy4wKQpHUk9VUCBCWSBgb3JpZ2luYCwgYG1vbnRoYCkgYExIU2AKSU5ORVIgSk9JTiBgYWlycG9ydHNgIEFTIGBSSFNgCk9OIChgTEhTYC5gb3JpZ2luYCA9IGBSSFNgLmBmYWFgKQopYGRicGx5cl8wMjdgIikpCmBgYAoKCgpgYGB7cn0Kc2hvd19xdWVyeShhaXJwb3J0X3N1bW1hcnkpCmBgYAoKCgpgYGB7cn0KYWlycG9ydF9zdW1tYXJ5X2NvbGxlY3QgPC0gY29sbGVjdChhaXJwb3J0X3N1bW1hcnkpCmBgYAoKICAtIFdpdGggdGhlIGRhdGFzZXQgeW91IHdyb3RlIG91dCwgY3JlYXRlIGEgZ3JhcGggdGhhdCBoZWxwcyBpbGx1c3RyYXRlIHRoZSAid29yc3QiIGFpcnBvcnRzIGluIHRlcm1zIG9mIGxhdGUgYXJyaXZhbHMuIFlvdSBoYXZlIHNvbWUgZnJlZWRvbSBpbiBob3cgeW91IGRlZmluZSB3b3JzdCBhbmQgeW91IG1heSB3YW50IHRvIGNvbnNpZGVyIHNvbWUgb2YgdGhlIG90aGVyIHZhcmlhYmxlcyB5b3UgY29tcHV0ZWQuIERvIHNvbWUgdGhlbWluZyB0byBtYWtlIHlvdXIgZ3JhcGggbG9vayBnbGFtb3JvdXMgKHRob3NlIG9mIHlvdSB3aG8gd2VyZW4ndCBpbiBteSBpbnRybyBkYXRhIHNjaWVuY2UgY2xhc3MgdGhpcyB5ZWFyIG1heSB3YW50IHRvIHdhdGNoIFdpbGwgQ2hhc2UncyBbR2xhbW91ciBvZiBHcmFwaGljc10oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1oNWNUYWNhV0U2SSkgdGFsayBmb3IgaW5zcGlyYXRpb24pLiAKICAKCiAgCmBgYHtyfQphaXJwb3J0X3N1bW1hcnlfY29sbGVjdDIgPC0gYWlycG9ydF9zdW1tYXJ5X2NvbGxlY3QgJT4lIAogIG11dGF0ZShtb250aCA9IGFzLmZhY3Rvcihtb250aCkpICU+JSAKICBmaWx0ZXIodG90YWxfZGVwYXJ0dXJlcyA+PSAyMCkKYGBgCiAgCiAgCiAgCmBgYHtyfQpwbG90IDwtIGdncGxvdChhaXJwb3J0X3N1bW1hcnlfY29sbGVjdDIsIGFlcyh4ID0gcHJvcF9sYXRlX292ZXIyMCwKICAgICAgICAgICAgIHkgPSBhdmdfZGlzdCwgY29sb3IgPSBuYW1lLCBzaGFwZSA9IG1vbnRoKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKyAKICB4bGFiKCJQcm9wb3J0aW9uIG9mIEZsaWdodHMgMjArIE1pbnV0ZXMgTGF0ZSIpICsKICB5bGFiKCJBdmVyYWdlIERpc3RhbmNlIFRyYXZlbGxlZCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmdncGxvdGx5KHBsb3QpCmBgYAogIAogIC0gQWx0aG91Z2ggeW91ciBncmFwaCB3YXMgdHJ1bHkgaW5zcGlyYXRpb25hbCwgeW91J3ZlIGJlZW4gcmVxdWVzdGVkIHRvICJib2lsIGl0IGRvd24gdG8gYSBmZXcgbnVtYmVycy4iIFNvbWUgcGVvcGxlIGp1c3QgZG9uJ3QgYXBwcmVjaWF0ZSBhbGwgdGhhdCBlZmZvcnQgeW91IHB1dCBpbi4gQW5kLCB5b3UgbmVlZCB0byB1c2UgdGhlIGFscmVhZHkgc3VtbWFyaXplZCBkYXRhIHRoYXQgeW91IGFscmVhZHkgcHVsbGVkIGluIGZyb20gU1FMLiBDcmVhdGUgYSB0YWJsZSB3aXRoIDYgb3IgZmV3ZXIgcm93cyBhbmQgMyBvciBmZXdlciBjb2x1bW5zIHRoYXQgc3VtbWFyaXplcyB3aGljaCBhaXJwb3J0IGlzIHRoZSAid29yc3QiIGluIHRlcm1zIG9mIGxhdGUgYXJyaXZhbHMuIEJlIGNhcmVmdWwgd2l0aCB5b3VyIGNhbGN1bGF0aW9ucy4gWW91IG1heSBjb25zaWRlciB1c2luZyB0aGUgYGthYmxlYCwgYGthYmxlRXh0cmFgLCBvciBgZ3RgIHBhY2thZ2VzIHRvIG1ha2UgeW91ciB0YWJsZSBsb29rIHRydWx5IHNwZWN0YWN1bGFyLgogIAogIApgYGB7cn0KYWlycG9ydF9zdW1tYXJ5X2NvbGxlY3QyICU+JSAKICBmaWx0ZXIobmFtZSAlaW4lIGMoIk5hbnR1Y2tldCBNZW0iLCAiQmFuZ29yIEludGwiLCAiQ2hpcHBld2EgQ291bnR5IEludGVybmF0aW9uYWwgQWlycG9ydCIsICJTb3V0aHdlc3QgT3JlZ29uIFJlZ2lvbmFsIEFpcnBvcnQiLCAiUmVkZGluZyBNdW5pIiwgIktleSBGaWVsZCIpKSAlPiUgCiAgZ3JvdXBfYnkobmFtZSkgJT4lIAogIHN1bW1hcmlzZShgUHJvcG9ydGlvbiBvZiBGbGlnaHRzIDIwKyBNaW51dGVzIExhdGUgKFdvcnN0IE1vbnRoKWAgPSBtYXgocHJvcF9sYXRlX292ZXIyMCksIGBBdmVyYWdlIERpc3RhbmNlIFRyYXZlbGxlZGAgPSBtZWFuKGF2Z19kaXN0KSkKYGBgCgogIAoyLiBDb21lIHVwIHdpdGggeW91ciBvd24gaW50ZXJlc3RpbmcgcXVlc3Rpb24gdGhhdCBkYXRhIGluIHRoZSBhaXJsaW5lcyBkYXRhYmFzZSBjYW4gaGVscCB5b3UgYW5zd2VyLiBXcml0ZSBhIFNRTCBxdWVyeSBhbmQgZXF1aXZhbGVudCBSIGNvZGUgY2h1bmsgdG8gZXh0cmFjdCB0aGUgZGF0YSB5b3UgbmVlZCBhbmQgY3JlYXRlIGFuIGVsZWdhbnQgZ3JhcGggdG8gaGVscCBhbnN3ZXIgdGhlIHF1ZXN0aW9uLiBCZSBzdXJlIHRvIHdyaXRlIGRvd24gdGhlIHF1ZXN0aW9uIHNvIGl0IGlzIGNsZWFyLiAKCioqV2hhdCBjYXJyaWVyIGlzIHdvcnN0PyBXb3JzdCBkZXRlcm1pbmVkIGJ5IHByb3BvcnRpb24gb2YgZmxpZ2h0cyBjYW5jZWxlZCBhbmQgYXZlcmFnZSBkaXN0YW5jZSB0cmF2ZWxlZCAodG8gYWNjb3VudCBmb3IgdGhlIGZhY3QgdGhhdCBzaG9ydGVyIGZsaWdodHMgc2hvdWxkIGdldCBjYW5jZWxlZCBsZXNzIG9mdGVuKS4qKgoKYGBge3J9CmNhbmNlbGxlZCA8LSAgdGJsKGNvbl9haXIsICJmbGlnaHRzIikgJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMTcpICU+JSAKICBncm91cF9ieShjYXJyaWVyLCBtb250aCkgJT4lCiAgc3VtbWFyaXNlKGF2Z19kaXN0ID0gbWVhbihkaXN0YW5jZSksCiAgICAgICAgICAgIHByb3BfY2FuY2VsbGVkID0gbWVhbihjYW5jZWxsZWQgPT0gMSksCiAgICAgICAgICAgIHRvdGFsX2RlcGFydHVyZXMgPSBuKCkpICU+JSAKICBpbm5lcl9qb2luKHRibChjb25fYWlyLCAiY2FycmllcnMiKSwKICAgICAgICAgICAgIGJ5ID0gYygiY2FycmllciIgPSAiY2FycmllciIpKSAKY2FuY2VsbGVkCmBgYAoKYGBge3J9CnNob3dfcXVlcnkoY2FuY2VsbGVkKQpgYGAKCmBgYHtyfQpjYW5jZWxsZWRfcXVlcnkgPC0gCiAgdGJsKGNvbl9haXIsCiAgICAgIHNxbCgiU0VMRUNUIGBMSFNgLmBjYXJyaWVyYCBBUyBgY2FycmllcmAsIGBMSFNgLmBtb250aGAgQVMgYG1vbnRoYCwgYExIU2AuYGF2Z19kaXN0YCBBUyBgYXZnX2Rpc3RgLCBgTEhTYC5gcHJvcF9jYW5jZWxsZWRgIEFTIGBwcm9wX2NhbmNlbGxlZGAsIGBMSFNgLmB0b3RhbF9kZXBhcnR1cmVzYCBBUyBgdG90YWxfZGVwYXJ0dXJlc2AsIGBSSFNgLmBuYW1lYCBBUyBgbmFtZWAKRlJPTSAoU0VMRUNUIGBjYXJyaWVyYCwgYG1vbnRoYCwgQVZHKGBkaXN0YW5jZWApIEFTIGBhdmdfZGlzdGAsIEFWRyhgY2FuY2VsbGVkYCA9IDEuMCkgQVMgYHByb3BfY2FuY2VsbGVkYCwgQ09VTlQoKikgQVMgYHRvdGFsX2RlcGFydHVyZXNgCkZST00gYGZsaWdodHNgCldIRVJFIChgeWVhcmAgPSAyMDE3LjApCkdST1VQIEJZIGBjYXJyaWVyYCwgYG1vbnRoYCkgYExIU2AKSU5ORVIgSk9JTiBgY2FycmllcnNgIEFTIGBSSFNgCk9OIChgTEhTYC5gY2FycmllcmAgPSBgUkhTYC5gY2FycmllcmApIgopKQpgYGAKCgpgYGB7cn0KY2FuY2VsbGVkX2NvbGxlY3QgPC0gY29sbGVjdChjYW5jZWxsZWQpCmBgYAoKYGBge3J9CmNhbmNlbGxlZF9jb2xsZWN0MiA8LSBjYW5jZWxsZWRfY29sbGVjdCAlPiUgCiAgbXV0YXRlKG1vbnRoID0gYXMuZmFjdG9yKG1vbnRoKSkKYGBgCgoKCmBgYHtyfQpwbG90IDwtIGdncGxvdChjYW5jZWxsZWRfY29sbGVjdDIsIGFlcyh4ID0gcHJvcF9jYW5jZWxsZWQsCiAgICAgICAgICAgICB5ID0gYXZnX2Rpc3QsIGNvbG9yID0gbmFtZSwgc2hhcGUgPSBtb250aCkpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAgeGxhYigiUHJvcG9ydGlvbiBvZiBGbGlnaHRzIENhbmNlbGxlZCIpICsKICB5bGFiKCJBdmVyYWdlIERpc3RhbmNlIFRyYXZlbGxlZCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmdncGxvdGx5KHBsb3QpCmBgYAoKKipTcGlyaXQsIEV4cHJlc3MsIGFuZCBKZXQgQmx1ZSBhcmUgdGhlIHdvcnN0IGFpcmxpbmVzLioqCgpgYGB7cn0KZGJEaXNjb25uZWN0KGNvbl9haXIpCmBgYAoKCgojIyBGdW5jdGlvbiBGcmlkYXkKCklmIHlvdSBuZWVkIHRvIHJldmlzaXQgdGhlIG1hdGVyaWFsLCBpdCBpcyBwb3N0ZWQgb24gdGhlIG1vb2RsZSBwYWdlLiBJJ3ZlIHRyaWVkIHRvIGFkZCBhbGwgdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMgdG8gdGhlIHRvcCwgYnV0IEkgbWF5IGhhdmUgbWlzc2VkIHNvbWV0aGluZy4KCioqYGdlb21fc2YoKWAgdGFza3MqKjoKClVzaW5nIHRoZSBleGFtcGxlIGZyb20gY2xhc3MgdGhhdCB3ZSBwcmVzZW50ZWQgYXMgYSBiYXNlbGluZSAob3IgeW91ciBvd24gaWYgeW91IHJlYWxseSB3YW50IHRvIGJlIGFtYml0aW91cyksIHRyeSB0byBhZGQgdGhlIGZvbGxvd2luZyBjb21wb25lbnRzIHRvIHRoZSBtYXAgb2YgdGhlIGNvbnRpZ3VvdXMgVW5pdGVkIFN0YXRlczoKCjEuCUNoYW5nZSB0aGUgY29sb3Igc2NoZW1lIG9mIHRoZSBtYXAgZnJvbSB0aGUgZGVmYXVsdCBibHVlIChvbmUgb3B0aW9uIGNvdWxkIGJlIHZpcmlkaXMpLgoyLglBZGQgYSBkb3QgKG9yIGFueSBzeW1ib2wgeW91IHdhbnQpIHRvIHRoZSBjZW50cm9pZCBvZiBlYWNoIHN0YXRlLgozLglBZGQgYSBsYXllciBvbnRvIHRoZSBtYXAgd2l0aCB0aGUgY291bnRpZXMuCjQuCUNoYW5nZSB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIG1hcCB0byB6b29tIGluIG9uIHlvdXIgZmF2b3JpdGUgc3RhdGUuCgoKYGBge3J9CnN0YXRlcyA8LSBzdF9hc19zZihtYXBzOjptYXAoInN0YXRlIiwgCnBsb3QgPSBGQUxTRSwgCmZpbGwgPSBUUlVFKSkKaGVhZChzdGF0ZXMpCmdncGxvdChkYXRhID0gc3RhdGVzKSArCiAgICBnZW9tX3NmKGZpbGwgPSBOQSkgKwogICAgY29vcmRfc2YoeGxpbSA9IGMoLTEyNywgLTYzKSwgeWxpbSA9IGMoMjQsIDUxKSwgZXhwYW5kID0gRkFMU0UpCnN0YXRlcyA8LSBzdGF0ZXMgJT4lCiAgbXV0YXRlKGFyZWEgPSBhcy5udW1lcmljKHN0X2FyZWEoc3RhdGVzKSkpCmdncGxvdChkYXRhID0gc3RhdGVzKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gYXJlYSkpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjcsIC02MyksIAp5bGltID0gYygyNCwgNTEpLCAKZXhwYW5kID0gRkFMU0UpCmBgYAoKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IGFyZWEpKSArCiAgICBjb29yZF9zZih4bGltID0gYygtMTI3LCAtNjMpLCAKeWxpbSA9IGMoMjQsIDUxKSwgCmV4cGFuZCA9IEZBTFNFKSArCnNjYWxlX2ZpbGxfdmlyaWRpc19iKCkKYGBgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBzdGF0ZXMpICsKICAgIGdlb21fc2YoYWVzKGZpbGwgPSBhcmVhKSkgKwogIHN0YXRfc2ZfY29vcmRpbmF0ZXMoKSArCiAgICBjb29yZF9zZih4bGltID0gYygtMTI3LCAtNjMpLCAKeWxpbSA9IGMoMjQsIDUxKSwgCmV4cGFuZCA9IEZBTFNFKQpgYGAKCgpgYGB7cn0KY291bnRpZXMgPC0gc3RfYXNfc2YobWFwKCJjb3VudHkiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFKSkKY291bnRpZXMkYXJlYSA8LSBhcy5udW1lcmljKHN0X2FyZWEoY291bnRpZXMpKQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogICAgZ2VvbV9zZihkYXRhID0gY291bnRpZXMsIGFlcyhmaWxsID0gYXJlYSkpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjcsIC02MyksIAp5bGltID0gYygyNCwgNTEpLCAKZXhwYW5kID0gRkFMU0UpICsKc2NhbGVfZmlsbF92aXJpZGlzX2IoKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHN0YXRlcykgKwogICAgZ2VvbV9zZihkYXRhID0gY291bnRpZXMsIGFlcyhmaWxsID0gYXJlYSkpICsKICAgIGNvb3JkX3NmKHhsaW0gPSBjKC0xMjQuMjEsIC0xMTMuMTkpLCAKeWxpbSA9IGMoMzIuNTMsIDQxLjk5OCksIApleHBhbmQgPSBGQUxTRSkgKwpzY2FsZV9maWxsX3ZpcmlkaXNfYigpCmBgYAoKCgoKCkhpbnQ6IGh0dHBzOi8vd3d3LnItc3BhdGlhbC5vcmcvci8yMDE4LzEwLzI1L2dncGxvdDItc2YtMi5odG1sIGlzIGEgdXNlZnVsIHJlZmVyZW5jZSBmb3Igc29tZSBvZiB0aGUgcXVlc3Rpb25zCgoqKmB0aWR5dGV4dGAgdGFza3MqKjoKCk5vdyB5b3Ugd2lsbCB0cnkgdXNpbmcgdGlkeXRleHQgb24gYSBuZXcgZGF0YXNldCBhYm91dCBSdXNzaWFuIFRyb2xsIHR3ZWV0cy4KCiMjIyMgUmVhZCBhYm91dCB0aGUgZGF0YQoKVGhlc2UgYXJlIHR3ZWV0cyBmcm9tIFR3aXR0ZXIgaGFuZGxlcyB0aGF0IGFyZSBjb25uZWN0ZWQgdG8gdGhlIEludGVybmV0IFJlc2VhcmNoIEFnZW5jeSAoSVJBKSwgYSBSdXNzaWFuICJ0cm9sbCBmYWN0b3J5LiIgIFRoZSBtYWpvcml0eSBvZiB0aGVzZSB0d2VldHMgd2VyZSBwb3N0ZWQgZnJvbSAyMDE1LTIwMTcsIGJ1dCB0aGUgZGF0YXNldHMgZW5jb21wYXNzIHR3ZWV0cyBmcm9tIEZlYnJ1YXJ5IDIwMTIgdG8gTWF5IDIwMTguCgpUaHJlZSBvZiB0aGUgbWFpbiBjYXRlZ29yaWVzIG9mIHRyb2xsIHR3ZWV0IHRoYXQgd2Ugd2lsbCBiZSBmb2N1c2luZyBvbiBhcmUgTGVmdCBUcm9sbHMsIFJpZ2h0IFRyb2xscywgYW5kIE5ld3MgRmVlZC4gICoqTGVmdCBUcm9sbHMqKiB1c3VhbGx5IHByZXRlbmQgdG8gYmUgQkxNIGFjdGl2aXN0cywgYWltaW5nIHRvIGRpdmlkZSB0aGUgZGVtb2NyYXRpYyBwYXJ0eSAoaW4gdGhpcyBjb250ZXh0LCBiZWluZyBwcm8tQmVybmllIHNvIHRoYXQgdm90ZXMgYXJlIHRha2VuIGF3YXkgZnJvbSBIaWxsYXJ5KS4gICoqUmlnaHQgdHJvbGxzKiogaW1pdGF0ZSBUcnVtcCBzdXBwb3J0ZXJzLCBhbmQgKipOZXdzIEZlZWQqKiBoYW5kbGVzIGFyZSAibG9jYWwgbmV3cyBhZ2dyZWdhdG9ycywiIHR5cGljYWxseSBsaW5raW5nIHRvIGxlZ2l0aW1hdGUgbmV3cy4KCkZvciBvdXIgdXBjb21pbmcgYW5hbHlzZXMsIHNvbWUgaW1wb3J0YW50IHZhcmlhYmxlcyBhcmU6CgogICogKiphdXRob3IqKiAoaGFuZGxlIHNlbmRpbmcgdGhlIHR3ZWV0KQogICogKipjb250ZW50KiogKHRleHQgb2YgdGhlIHR3ZWV0KQogICogKipsYW5ndWFnZSoqIChsYW5ndWFnZSBvZiB0aGUgdHdlZXQpCiAgKiAqKnB1Ymxpc2hfZGF0ZSoqIChkYXRlIGFuZCB0aW1lIHRoZSB0d2VldCB3YXMgc2VudCkKClZhcmlhYmxlIGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIG9uIFtHaXRodWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9maXZldGhpcnR5ZWlnaHQvcnVzc2lhbi10cm9sbC10d2VldHMvKSBhbmQgYSBtb3JlIGRldGFpbGVkIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhc2V0IGNhbiBiZSBmb3VuZCBpbiB0aGlzIFtmaXZldGhpcnR5ZWlnaHQgYXJ0aWNsZV0oaHR0cHM6Ly9maXZldGhpcnR5ZWlnaHQuY29tL2ZlYXR1cmVzL3doeS13ZXJlLXNoYXJpbmctMy1taWxsaW9uLXJ1c3NpYW4tdHJvbGwtdHdlZXRzLykuCgpCZWNhdXNlIHRoZXJlIGFyZSAxMiBkYXRhc2V0cyBjb250YWluaW5nIDIsOTczLDM3MSB0d2VldHMgc2VudCBieSAyLDg0OCBUd2l0dGVyIGhhbmRsZXMgaW4gdG90YWwsIHdlIHdpbGwgYmUgdXNpbmcgdGhyZWUgb2YgdGhlc2UgZGF0YXNldHMgKG9uZSBmcm9tIGEgUmlnaHQgdHJvbGwsIG9uZSBmcm9tIGEgTGVmdCB0cm9sbCwgYW5kIG9uZSBmcm9tIGEgTmV3cyBGZWVkIGFjY291bnQpLgoKXApcCgoxLiBSZWFkIGluIFRyb2xsIFR3ZWV0cyBEYXRhc2V0IC0gdGhpcyB0YWtlcyBhIHdoaWxlLiBZb3UgY2FuIGNhY2hlIGl0IHNvIHlvdSBkb24ndCBuZWVkIHRvIHJlYWQgaXQgaW4gYWdhaW4gZWFjaCB0aW1lIHlvdSBrbml0LiBCZSBzdXJlIHRvIHJlbW92ZSB0aGUgYGV2YWw9RkFMU0VgISEhIQoKYGBge3IsIGNhY2hlPVRSVUV9CnRyb2xsX3R3ZWV0cyA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2ZpdmV0aGlydHllaWdodC9ydXNzaWFuLXRyb2xsLXR3ZWV0cy9tYXN0ZXIvSVJBaGFuZGxlX3R3ZWV0c18xMi5jc3YiKQpgYGAKCjIuIEJhc2ljIERhdGEgQ2xlYW5pbmcgYW5kIEV4cGxvcmF0aW9uCgogIGEuIFJlbW92ZSByb3dzIHdoZXJlIHRoZSB0d2VldCB3YXMgaW4gYSBsYW5ndWFnZSBvdGhlciB0aGFuIEVuZ2xpc2gKICBiLiBSZXBvcnQgdGhlIGRpbWVuc2lvbnMgb2YgdGhlIGRhdGFzZXQKICBjLiBDcmVhdGUgdHdvIG9yIHRocmVlIGJhc2ljIGV4cGxvcmF0b3J5IHBsb3RzIG9mIHRoZSBkYXRhIChleC4gcGxvdCBvZiB0aGUgZGlmZmVyZW50IGxvY2F0aW9ucyBmcm9tIHdoaWNoIHR3ZWV0cyB3ZXJlIHBvc3RlZCwgcGxvdCBvZiB0aGUgYWNjb3VudCBjYXRlZ29yeSBvZiBhIHR3ZWV0KQogIApgYGB7cn0KdHJvbGxfdHdlZXRzMiA8LSB0cm9sbF90d2VldHMgJT4lIAogIGZpbHRlcihsYW5ndWFnZSA9PSAiRW5nbGlzaCIpCmBgYAoKYGBge3J9CnRyb2xsX3R3ZWV0czIgJT4lIAogIGRpbSgpCmBgYAoKCmBgYHtyfQp0cm9sbF90d2VldHMyICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBhY2NvdW50X3R5cGUpKSArCiAgZ2VvbV9iYXIoKQpgYGAKCmBgYHtyfQp0cm9sbF90d2VldHMyICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByZXR3ZWV0KSkgKwogIGdlb21fYmFyKCkKYGBgCgoKCgozLiBVbm5lc3QgVG9rZW5zCgpXZSB3YW50IGVhY2ggcm93IHRvIHJlcHJlc2VudCBhIHdvcmQgZnJvbSBhIHR3ZWV0LCByYXRoZXIgdGhhbiBhbiBlbnRpcmUgdHdlZXQuIEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhCgpgYGB7cn0KdHJvbGxfdHdlZXRzX3VudG9rZW4gPC0gdHJvbGxfdHdlZXRzMiAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsY29udGVudCkKdHJvbGxfdHdlZXRzX3VudG9rZW4KYGBgCgpcClwKCjQuIFJlbW92ZSBzdG9wd29yZHMuIEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhCgpgYGB7cn0KI2dldCByaWQgb2Ygc3RvcHdvcmRzICh0aGUsIGFuZCwgZXRjLikKdHJvbGxfdHdlZXRzX2NsZWFuZWQgPC0gdHJvbGxfdHdlZXRzX3VudG9rZW4gJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCmBgYAoKVGFrZSBhIGxvb2sgYXQgdGhlIHRyb2xsX3R3ZWV0c19jbGVhbmVkIGRhdGFzZXQuICBBcmUgdGhlcmUgYW55IG90aGVyIHdvcmRzL2xldHRlcnMvbnVtYmVycyB0aGF0IHdlIHdhbnQgdG8gZWxpbWluYXRlIHRoYXQgd2VyZW4ndCB0YWtlbiBjYXJlIG9mIGJ5IHN0b3Bfd29yZHM/IEJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhCgpgYGB7cn0KI2dldCByaWQgb2YgaHR0cCwgaHR0cHMsIHQuY28sIHJ0LCBhbXAsIHNpbmdsZSBudW1iZXIgZGlnaXRzLCBhbmQgc2luZ3VsYXIgbGV0dGVycwp0cm9sbF90d2VldHNfY2xlYW5lZCA8LSB0cm9sbF90d2VldHNfY2xlYW5lZCAlPiUKICBmaWx0ZXIod29yZCAhPSAnaHR0cHMnKSAlPiUgCiAgZmlsdGVyKHdvcmQgIT0gInQuY28iKSAlPiUgCiAgZmlsdGVyKHdvcmQgIT0gImh0dHAiKSAlPiUgCiAgZmlsdGVyKHdvcmQgIT0gInRydW1wIikgJT4lIAogIGZpbHRlcih3b3JkICE9ICJ0cnVtcCdzIikKYGBgCgoKNS4gTG9vayBhdCBhIHN1YnNldCBvZiB0aGUgdHdlZXRzIHRvIHNlZSBob3cgb2Z0ZW4gdGhlIHRvcCB3b3JkcyBhcHBlYXIuCgpgYGB7cn0KdHJvbGxfdHdlZXRzX3NtYWxsIDwtIHRyb2xsX3R3ZWV0c19jbGVhbmVkICU+JQogIGNvdW50KHdvcmQpICU+JQogIHNsaWNlX21heChvcmRlcl9ieSA9IG4sIG4gPSA1MCkgIyA1MCBtb3N0IG9jY3VycmluZyB3b3JkcwojIHZpc3VhbGl6ZSB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoZSA1MCB0b3Agd29yZHMgYXBwZWFyCmdncGxvdCh0cm9sbF90d2VldHNfc21hbGwsIAogICAgICAgYWVzKHkgPSBmY3RfcmVvcmRlcih3b3JkLG4pLCB4ID0gbikpICsKICBnZW9tX2NvbCgpCmBgYAoKCjYuIFNlbnRpbWVudCBBbmFseXNpcwoKICBhLiBHZXQgdGhlIHNlbnRpbWVudHMgdXNpbmcgdGhlICJiaW5nIiBwYXJhbWV0ZXIgKHdoaWNoIGNsYXNzaWZpZXMgd29yZHMgaW50byAicG9zaXRpdmUiIG9yICJuZWdhdGl2ZSIpCiAgYi4gUmVwb3J0IGhvdyBtYW55IHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyB0aGVyZSBhcmUgaW4gdGhlIGRhdGFzZXQuICBBcmUgdGhlcmUgbW9yZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSB3b3JkcywgYW5kIHdoeSBkbyB5b3UgdGhpbmsgdGhpcyBtaWdodCBiZT8KICAKQmUgc3VyZSB0byByZW1vdmUgdGhlIGBldmFsPUZBTFNFYCEhISEKCmBgYHtyfQojIGxvb2sgYXQgc2VudGltZW50CnNlbnRpbWVudHMgPC0gZ2V0X3NlbnRpbWVudHMoImJpbmciKQojIGFzc2lnbiBhIHNlbnRpbWVudCB0byBlYWNoIHdvcmQgdGhhdCBoYXMgb25lIGFzc29jaWF0ZWQKdHJvbGxfdHdlZXRzX3NlbnRpbWVudCA8LSB0cm9sbF90d2VldHNfY2xlYW5lZCAlPiUKICBpbm5lcl9qb2luKHNlbnRpbWVudHMpCiMgY291bnQgdGhlIHNlbnRpbWVudHMKdHJvbGxfdHdlZXRzX3NlbnRpbWVudCAlPiUgCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpKQpgYGAKCjcuIFVzaW5nIHRoZSB0cm9sbF90d2VldHNfc21hbGwgZGF0YXNldCwgbWFrZSBhIHdvcmRjbG91ZDoKCiAgYS4gVGhhdCBpcyBzaXplZCBieSB0aGUgbnVtYmVyIG9mIHRpbWVzIHRoYXQgYSB3b3JkIGFwcGVhcnMgaW4gdGhlIHR3ZWV0cwogIGIuIFRoYXQgaXMgY29sb3JlZCBieSBzZW50aW1lbnQgKHBvc2l0aXZlIG9yIG5lZ2F0aXZlKQoKCkJlIHN1cmUgdG8gcmVtb3ZlIHRoZSBgZXZhbD1GQUxTRWAhISEhCgpgYGB7cn0KIyBtYWtlIGEgd29yZGNsb3VkIHdoZXJlIHRoZSBzaXplIG9mIHRoZSB3b3JkIGlzIGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgdGltZXMgdGhlIHdvcmQgYXBwZWFycyBhY3Jvc3MgdGhlIHR3ZWV0cwp0cm9sbF90d2VldHNfc21hbGwgJT4lCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gNTApKQojIG1ha2UgYSB3b3JkY2xvdWQgY29sb3JlZCBieSBzZW50aW1lbnQKdHJvbGxfdHdlZXRzX3NlbnRpbWVudCAlPiUKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUKICBhY2FzdCh3b3JkIH4gc2VudGltZW50LCB2YWx1ZS52YXIgPSAibiIsIGZpbGwgPSAwKSAlPiUKICBjb21wYXJpc29uLmNsb3VkKGNvbG9ycyA9IGMoInJlZCIsImdyZWVuIiksCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSA1MCkKYGBgCgpBcmUgdGhlcmUgYW55IHdvcmRzIHdob3NlIGNhdGVnb3JpemF0aW9uIGFzICJwb3NpdGl2ZSIgb3IgIm5lZ2F0aXZlIiBzdXJwcmlzZWQgeW91PwpcCioqSXQgd2FzIGNhdGVnb3JpemluZyB0cnVtcCBhcyBwb3NpdGl2ZSwgaXQgd2FzIHByb2JhYmx5IGJlY2F1c2UgdGhlIG5vdW4gb3IgdmVyYiBmb3JtIG9mIHRoZSB3b3JkIGFuZCBub3QgdHJ1bXBzIG5hbWUsIHNvIEkgcmVtb3ZlZCBpdCBkdWUgdG8gY29udGV4dC4gKioKCgojIyBQcm9qZWN0cwoKUmVhZCB0aGUgcHJvamVjdCBkZXNjcmlwdGlvbiBvbiB0aGUgbW9vZGxlIHBhZ2UuIFRhbGsgdG8geW91ciBncm91cCBtZW1iZXJzIGFib3V0IHBvdGVudGlhbCB0b3BpY3MuIAoKKipUYXNrOioqCgpXcml0ZSBhIHNob3J0IHBhcmFncmFwaCBhYm91dCBpZGVhcyB5b3UgaGF2ZS4gSWYgeW91IGFscmVhZHkgaGF2ZSBzb21lIGRhdGEgc291cmNlcyBpbiBtaW5kLCB5b3UgY2FuIGxpbmsgdG8gdGhvc2UsIGJ1dCBJJ20gbW9yZSBjb25jZXJuZWQgd2l0aCB5b3UgaGF2aW5nIGEgdG9waWMgdGhhdCB5b3UncmUgaW50ZXJlc3RlZCBpbiBpbnZlc3RpZ2F0aW5nIHJpZ2h0IG5vdy4gCgoqKldlIHdpbGwgcHJvYmFibHkgZG8gc29tZXRoaW5nIHdpdGggc29jY2VyLCB3aGljaCBpcyBhIGxpdHRsZSBiaXQgb3V0c2lkZSBvZiBhbGwgb2Ygb3VyIGNvbWZvcnQgem9uZXMgYXMgdGhvc2Ugb2YgdXMgd2hvIHByaW1hcmlseSB3b3JrIHdpdGggc3BvcnRzIGFuYWx5c2lzIGZvY3VzIG9uIGJhc2ViYWxsLiAgV2Ugd2VyZSB0aGlua2luZyBvZiBtYWtpbmcgYSBtb2RlbCB0aGF0IHByZWRpY3RzIEZJRkEgVWx0aW1hdGUgVGVhbSdzIHRlYW0gb2YgdGhlIHNlYXNvbiBwbGF5ZXJzIGJhc2VkIG9uIHJlYWwgd29ybGQgc3RhdHMgYW5kIHRlYW0gcGVyZm9ybWFuY2UuICBPciBwcmVkaWN0aW5nIHNvY2NlciByZXN1bHRzIG9yIHNvbWV0aGluZyBhbG9uZyB0aG9zZSBsaW5lcy4gIFdlIHdvdWxkIG5lZWQgdG8gbGVhcm4gc29tZSBkYXRhIHNjcmFwaW5nIHRlY2huaXF1ZXMgc28gdGhhdCB3b3VsZCBiZSBhIGJyaWRnZSB3ZSB3b3VsZCBoYXZlIHRvIGNyb3NzIGlmIHdlIGdvIGZvcndhcmQgd2l0aCB0aGUgcHJvamVjdCBvbiB0aGVzZSB0b3BpY3MuKioKCiMjICJVbmRvaW5nIiBiaWFzCgoqKlRhc2s6KioKClJlYWQgdGhpcyB0d2VldCBbdGhyZWFkXShodHRwczovL3RocmVhZHJlYWRlcmFwcC5jb20vdGhyZWFkLzEzNzU5NTcyODQwNjEzNzY1MTYuaHRtbCkgYnkgW0RlYiBSYWppXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWJvcmFoX1JhamkpIHdobyB5b3UgbWF5IHJlbWVtYmVyIGZyb20gdGhlICpDb2RlZCBCaWFzKiBmaWxtLiBXcml0ZSBhIHNob3J0IHBhcmFncmFwaCB0aGF0IGRpc2N1c3NlcyBhdCBsZWFzdCBvbmUgb2YgdGhlIG1pc2NvbmNlcHRpb25zLgoKXAoqKlRoZSBtaXNjb25jZXB0aW9uIHRoYXQgbW9zdCBzdXJwcmlzZWQgbWUgd2FzIHRoYXQgcmFjZSBhbmQgZ2VuZGVyIGFyZSB0aGUgbGVhc3Qgb2J2aW91cyBiaWFzZXMgdG8gZGV0ZWN0LiAgSXQgbWFrZXMgc2Vuc2UgYWZ0ZXIgaGVhcmluZyB0aGF0IGhvd2V2ZXIgYmVjYXVzZSB0aGVyZSBhcmUgc28gbWFueSBtb2RlbHMgYmVpbmcgdXNlZCBub3cgdGhhdCBoYXZlIHRoZXNlIGlzc3VlcyB0aGF0IGFyZW4ndCBkZXRlY3RlZC4gIEp1c3QgYmVjYXVzZSB0aG9zZSBkYXRhIHBvaW50cyBhcmVuJ3QgaW4gdGhlIGRhdGEgcGVvcGxlIG1heSBiZWxpZXZlIHRoYXQgdGhlIGFsZ29yaXRobSBjYW5ub3QgcHJlZGljdCBvZmYgdGhvc2UgYXR0cmlidXRlcywgYnV0IHByb3hpZXMgZXhpc3QgYXMgUmFqaSBwb2ludHMgb3V0LiAgSXQgbWFrZXMgc2Vuc2UgdGhhdCB0aG9zZSBhdHRyaWJ1dGVzIG5vdCBpbiB0aGUgZGF0YSBhcmUgdGhlIGhhcmRlc3QgdG8gZmluZCB0aGUgYmlhc2VzIGZyb20sIGFzIHRoZSBwcm94aWVzIGRvIHRoZSB3b3JrLioqCg==